package spitfire.ksim.node;

import java.text.DateFormat;
import java.util.ArrayList;
import java.util.List;

import spitfire.ksim.network.KNetAddress;
import spitfire.ksim.network.KNetwork;
import spitfire.ksim.network.KNetworkModule;
import spitfire.ksim.simulator.KJob;
import spitfire.ksim.simulator.KSimObject;
import spitfire.ksim.simulator.KSimulator;
import spitfire.ksim.simulator.KStateChangeListener;
import spitfire.ksim.util.TPoint;

/**
 * <p>
 * KNode is the abstract base class for wireless sensor network nodes, including
 * {@link KGateway} and {@link KSensorNode}. KNode integrates a
 * {@link KNodeNetworkModule} and delegates network functions to the network module. 
 * </p>
 * 
 * <p>
 * KNode extends {@link KSimObject} and can schedule jobs for itself. 
 * </p>
 * 
 * @author Adam
 * 
 */
public abstract class KNode extends KSimObject {

	public static final double RADIO_RANGE = 150d;
	private String displayName = "Untitled";
	private TPoint location;
	private KNetworkModule networkModule;
	private List<KJob> initialJobList = new ArrayList<KJob>(); 
	private List<KStateChangeListener> scLstnrList = new ArrayList<KStateChangeListener>();  
	
	/**
	 * Handle packet receive event.
	 * Invoked when a piece of data is received from the {@link KNetwork}.
	 * @param srcAddr - source address of the host that send the data.
	 * @param payload - data pay load.
	 */
	public abstract void onPacketRecv(KNetAddress srcAddr, Object payload);
	
	/**
	 * Initialization work to do when simulation started.
	 */
	
	public KNode(KSimulator simulator, KNetwork network, TPoint location) {
		super(simulator);
		if (location == null) {
			throw new RuntimeException("Location cannot be null");
		}
		this.location = location;
		networkModule = new KNodeNetworkModule(this, network, RADIO_RANGE);
	}

	public void initNode() {
		for (KJob initialJob : initialJobList) {
			addOwnJob(initialJob);
		}
	}
	
	@Override
	public void destroy() {
		super.destroy();
		networkModule.disconnectNetwork();
		networkModule = null;
		scLstnrList.clear();
	}

	/**
	 * Send the given data as pay load to the given destination address.
	 * @param destination - destination address.
	 * @param payload - data to be sent.
	 */
	public void send(KNetAddress destination, Object payload) {
		System.out.println("[" + DateFormat.getDateTimeInstance().format(getSimTime())
				+ "] " + getDisplayName() + " sending to " + destination + ": " + payload);
		networkModule.send(destination, payload);
	}
	
	/**
	 * @return network module of this node.
	 */
	public KNetworkModule getNetworkModule() {
		return networkModule;
	}
	
	/**
	 * @return network address of this node.
	 */
	public KNetAddress getNetAddr() {
		return networkModule.getNetAddr();
	}

	/**
	 * Set the given name for display.
	 * @param displayName - the name for display.
	 */
	public void setDisplayName(String displayName) {
		this.displayName = displayName;
	}

	/**
	 * @return the display name.
	 */
	public String getDisplayName() {
		return displayName;
	}

	/**
	 * Set location of the node to the given location.
	 * @param location - location to set.
	 */
	public void setLocation(TPoint location) {
		if (location == null) {
			throw new NullPointerException();
		}
		this.location = location;
	}

	/**
	 * @return the current location of this node.
	 */
	public TPoint getLocation() {
		return location;
	}
	
	/**
	 * Add the given {@link KJob} to the initialJobList.
	 * @param job - the initial job to add.
	 */
	public void addInitialJob(KJob job) {
		initialJobList.add(job);
	}
	
	/**
	 * Remove the given {@link KJob} from the initialJObList.
	 * @param job - the initial job to remove.
	 */
	public void removeInitialJob(KJob job) {
		initialJobList.remove(job);
	}
	
	/**
	 * Add the given {@link KStateChangeListener} to this node.
	 * @param l - the listener to add.
	 */
	public void addStateChangeListener(KStateChangeListener l) {
		scLstnrList.add(l);
	}
	
	/**
	 * Remove the given {@link KStateChangeListener} from this node. 
	 * @param l - the listener to remove.
	 */
	public void removeStateChangeListener(KStateChangeListener l) {
		scLstnrList.remove(l);
	}
	
	/**
	 * Inform all {@link KStateChangeListener} of this node that state changes.
	 */
	public void invokeStateChange() {
		synchronized (scLstnrList) {
			for (KStateChangeListener l : scLstnrList) {
				l.stateChanged();
			}
		}
	}
}
